package ru.niir.goodfil.db.handlers;

import org.apache.log4j.Logger;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.StatelessSession;
import ru.niir.goodfil.audit.util.XMLUtils;
import ru.niir.goodfil.db.*;
import ru.niir.goodfil.exceptions.GoodfilRuntimeException;
import ru.niir.goodfil.exceptions.ValidationException;
import ru.niir.goodfil.exceptions.WrongHandlerException;
import ru.niir.goodfil.utils.Transliter;
import ru.niir.goodfil.utils.validation.*;

import java.math.BigInteger;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import static ru.niir.goodfil.utils.Utils._assertNotNull;

/**
 * Обработчик для заказов
 *
 * @see ru.niir.goodfil.db.Order заказы
 */
public class OrderHandler extends Handler {
    private Logger testLogger = Logger.getLogger("ru.niir.goodfil.ui.tasks.OrderIdGenerationTest");
    private Order oldOrder;

    /**
     * Конструктор
     */
    public OrderHandler(Goodfil gf) {
        super(Order.class, gf);
    }

    /**
     * Проверка заказа. 1. clientId указан верно (FK) 2. managerId указан верно (FK), указывает на менеджера 3.
     * deliveryTimeSpan >= 0 (UNSIGNED) 4. deliverySum >= 0 (UNSIGNED) 5. discountAbs = discountSum = 0 6. Заказ не
     * должен содержать товаров, у которых не указан код соответствия 1С
     * <p/>
     * Убраны проверки: 1. orderStateId в допустимом диапазоне (теперь только через FK) 2. deliveryTypeId в допустимом
     * диапазоне (теперь только через FK) 3. orderTypeId в допустимом диапазоне (теперь только через FK)
     */
    public ValidationResult validate(Object o) {
        assert o != null;
        if (!(o instanceof Order)) {
            throw new WrongHandlerException(klass, o.getClass());
        }

        Order order = (Order) o;
        ValidationResult result = ValidationResult.Ok(Order.class);

        if (gf.get(Client.class, order.getClientId()) == null) {
            result.addValidationError(new ValidationError("clientId", "Клиент", order.getClientId(), ValidationProblem.REFERENCE));
        }

        User manager = (User) gf.get(User.class, order.getManagerId());
        if (manager == null) {
            result.addValidationError(new ValidationError("managerId", "Менеджер", order.getManagerId(), ValidationProblem.REFERENCE));
        } else if (!manager.getIsManager()) {
            result.addValidationError(new ValidationError("managerId", "Менеджер", order.getManagerId(), ValidationProblem.REFERENCE, "С заказом сопоставлен пользователь, не являющийся менеджером"));
        }

        if (order.getDeliveryTimeSpan() < 0) {
            result.addValidationError(new ValidationError("deliveryTimeSpan", "Срок доставки", order.getDeliveryTimeSpan(), ValidationProblem.POSITIVE));
        }

        if (order.getDeliverySum() < 0) {
            result.addValidationError(new ValidationError("deliverySum", "Стоимость доставки", order.getDeliverySum(), ValidationProblem.POSITIVE));
        }

        order.setDiscountAbs(0);
        order.setDiscountPercent(0);

        //  Заказ не должен содержать товаров, для которых не установлен код соответствия 1С
        for (OrderItem orderItem : gf.getOrderItemsByOrderId(order.getId())) {
            Good good = gf.getGoodById(orderItem.getGoodId());
            if (good.getC1CodeIsMissing()) {
                result.addValidationError(new ValidationError("1CCode", "Код соответствия с 1С", good.getId(), ValidationProblem.REFERENCE, "В заказе присутствует товар, для которого не указан код соответствия 1С"));
            }
        }

        return result;
    }

    /**
     * Проверка того, что заказ может быть создан. Условия: 1. Клиент установлен верно (FK) 2. Менеджер установлен верно
     * (FK) 3. Клиент активен 4. Клиент принадлежит активной клиентской группе
     */
    public CreateCheckResult createCheck(Object o) throws ValidationException {
        assert o != null;
        if (!(o instanceof Order)) {
            throw new WrongHandlerException(klass, o.getClass());
        }

        Order order = (Order) o;

        Client client = (Client) gf.get(Client.class, order.getClientId());
        if (client == null) {
            throw new ValidationException(Order.class, "clientId", "Клиент", order.getClientId(), ValidationProblem.REFERENCE);
        }

        User manager = (User) gf.get(User.class, order.getManagerId());
        if (manager == null) {
            throw new ValidationException(Order.class, "managerId", "Менеджер", order.getManagerId(), ValidationProblem.REFERENCE);
        }

        if (!client.isActive()) {
            return CreateCheckResult.Error(klass, OrderCreationFailureType.CLIENT_IS_NOT_ACTIVE);
        }

//        ClientGroup clientGroup = (ClientGroup) gf.get(ClientGroup.class, client.getClientGroupId());
//        if (!clientGroup.isActive())
//            return CreateCheckResult.Error(klass, OrderCreationFailureType.CLIENT_GROUP_IS_NOT_ACTIVE);

        return CreateCheckResult.Ok(klass);
    }

    /**
     * Перед удалением зказа удаляются его позиции
     *
     * @param id идентификатор объекта, который собираемся удалить
     */
    public void preRemove(long id) {
        //  Это теперь сделают FK
//        getSession().createQuery("delete from OrderItem where orderId=?").setLong(0, id).executeUpdate();
    }

    private Set<BigInteger> existingIds;

    @Override
    public void preSave(Object o) {
        Order order = (Order) o;

        if (order.getId() != -1) {
            throw new GoodfilRuntimeException("preSave() call for order, but order id is " + order.getId());
        }

        existingIds = new HashSet<BigInteger>();
        List l = gf.getSession().createSQLQuery("select Id from Orders").list();
        Iterator i = l.iterator();
        while (i.hasNext()) {
            Object value = i.next();
            BigInteger id = (BigInteger) value;
            existingIds.add(id);
        }

    }

    @Override
    public void postSave(Object o) {
        Order order = (Order) o;

        testLogger.warn("saved: " + order.getId());

        if (existingIds.contains(new BigInteger(String.valueOf(order.getId())))) {
            throw new GoodfilRuntimeException("duplication of ids after presave");
        }

        Logger logger = Logger.getLogger("OrderStatusChange");
        Client client = gf.getClientById(order.getClientId());
        User manager = gf.getUserById(order.getManagerId());

        StringBuilder sb = new StringBuilder();
        sb.append("ORDER " + order.getId() + " STATUS CHANGED TO " + order.getOrderStateId());
        sb.append(" (client: " + client.getId() + " " + Transliter.translit(client.getCompanyName()) + ",");
        sb.append(" manager: " + manager.getId() + " " + Transliter.translit(manager.getFio()) + ",");
        sb.append(" user: " + Transliter.translit(gf.getUsername()) + ",");
        sb.append(" from: OrderHandler.postSave)");
        logger.debug(sb.toString());

        insertOrUpdateOrderDeliveryInfoExt(getSession(), order);
    }

    @Override
    public void postUpdate(Object o) {
        Order order = (Order) o;
        insertOrUpdateOrderDeliveryInfoExt(getSession(), order);

        String objectChangeXml = XMLUtils.getObjectChangeXml(oldOrder, order);

    }


    public static void insertOrUpdateOrderDeliveryInfoExt(StatelessSession session, Order order) {
        _assertNotNull(session);
        _assertNotNull(order);

        OrderDeliveryInfoExt orderDeliveryInfoExt = order.getDeliveryInfoExt();
        if (orderDeliveryInfoExt == null) {
            return;
        }
        if (orderDeliveryInfoExt.getOrderId() == -1) {
            orderDeliveryInfoExt.setOrderId(order.getId());
        }

        Query query = session.createSQLQuery("INSERT INTO `OrdersDeliveryInfoExt`(" +
                "`Id`, " +
                "`PayType`, " +
                "`TransportName`, " +
                "`TransportContactFio`," +
                "`TransportAddress`," +
                "`TransportTel`," +
                "`TargetAddress`," +
                "`TargetContactFio`," +
                "`TargetTel`," +
                "`Period`," +
                "`Comment`) VALUES (" +
                "?, " +
                "?, " +
                "?, " +
                "?, " +
                "?, " +
                "?, " +
                "?," +
                "?, " +
                "?, " +
                "?, " +
                "?) ON DUPLICATE KEY UPDATE " +
                "`PayType` = VALUES(`PayType`), " +
                "`TransportName` = VALUES(`TransportName`), " +
                "`TransportContactFio` = VALUES(`TransportContactFio`)," +
                "`TransportAddress` = VALUES(`TransportAddress`)," +
                "`TransportTel` = VALUES(`TransportTel`)," +
                "`TargetAddress` = VALUES(`TargetAddress`)," +
                "`TargetContactFio` = VALUES(`TargetContactFio`)," +
                "`TargetTel` = VALUES(`TargetTel`)," +
                "`Period` = VALUES(`Period`)," +
                "`Comment` = VALUES(`Comment`);")
                .setLong(0, order.getId())
                .setBoolean(1, order.getDeliveryInfoExt().getPayType())
                .setParameter(2, order.getDeliveryInfoExt().getTransportName(), Hibernate.STRING)
                .setParameter(3, order.getDeliveryInfoExt().getTransportContactFio(), Hibernate.STRING)
                .setParameter(4, order.getDeliveryInfoExt().getTransportAddress(), Hibernate.STRING)
                .setParameter(5, order.getDeliveryInfoExt().getTransportTel(), Hibernate.STRING)
                .setParameter(6, order.getDeliveryInfoExt().getTargetAddress(), Hibernate.STRING)
                .setParameter(7, order.getDeliveryInfoExt().getTargetContactFio(), Hibernate.STRING)
                .setParameter(8, order.getDeliveryInfoExt().getTargetTel(), Hibernate.STRING)
                .setInteger(9, order.getDeliveryInfoExt().getPeriod())
                .setParameter(10, order.getDeliveryInfoExt().getComment(), Hibernate.STRING);

        query.executeUpdate();
    }


    @Override
    public void preUpdate(Object o) {
        Order order = (Order) o;

        /*
        Добавляем информацию в лог
         */
        Logger logger = Logger.getLogger("OrderStatusChange");
        oldOrder = gf.getOrderById(order.getId());
        if (order.getOrderStateId() != oldOrder.getOrderStateId()) {
            Client client = gf.getClientById(order.getClientId());
            User manager = gf.getUserById(order.getManagerId());

            StringBuilder sb = new StringBuilder();
            sb.append("ORDER " + order.getId() + " STATUS CHANGED FROM " +
                    oldOrder.getOrderStateId() + " [" + Transliter.translit(oldOrder.getOrderStateAsString()) + "] " +
                    "TO " + order.getOrderStateId() + " [" + Transliter.translit(order.getOrderStateAsString()) + "]");
            sb.append(" (client: " + client.getId() + " " + Transliter.translit(client.getCompanyName()) + ",");
            sb.append(" manager: " + manager.getId() + " " + Transliter.translit(manager.getFio()) + ",");
            sb.append(" user: " + Transliter.translit(gf.getUsername()) + ",");
            sb.append(" from: OrderHandler.preUpdate)");
            logger.debug(sb.toString());
        }

        logger = Logger.getLogger("Order1CNumChange");
        if (!stringEquals(order.getC1Num(), oldOrder.getC1Num())) {
            Client client = gf.getClientById(order.getClientId());
            User manager = gf.getUserById(order.getManagerId());

            StringBuilder sb = new StringBuilder();
            sb.append("ORDER " + order.getId() + " C1 NUM CHANGED TO " + order.getC1Num() + " FROM " + oldOrder.getC1Num());
            sb.append(" (client: " + client.getId() + " " + Transliter.translit(client.getCompanyName()) + ", ");
            sb.append("manager: " + manager.getId() + " " + Transliter.translit(manager.getFio()) + ", ");
            sb.append("user: " + Transliter.translit(String.valueOf(gf.getUsername())) + ", ");
            sb.append("from: OrderHandler.preUpdate)");
            logger.debug(sb.toString());
        }
    }

    private boolean stringEquals(String s1, String s2) {
        if (s1 == null && s2 == null) {
            return true;
        } else if (s1 == null || s2 == null) {
            return false;
        } else {
            return s1.equals(s2);
        }
    }
}
